# -*- python -*-
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

Import('env')

egyptian_cotton_nexe = env.ComponentProgram('egyptian_cotton',
                                            'egyptian_cotton.c',
                                            EXTRA_LIBS=['${PTHREAD_LIBS}',
                                                        '${NONIRT_LIBS}'])

platform_limits_known = False

# this test accepts the number of threads to attempt to create as an
# argument. NACL_THREAD_MAX is 8192 for x86-32 and x86-64, and 4096
# for the Arm.  See the header files
# src/trusted/service_runtime/arch/{x86,arm}/sel_ldr_{x86,arm}.h for
# the current values (and update this comment to reflect reality!).
# On the x86_32, the actual number of threads possible is slightly
# fewer than 8192, since the service runtime's trusted internal
# threads use the same resource pool.  In practice, 8189 works as of
# this writing (2010-03-10), but applications should not rely on this
# going forward, and we only test for 8180 below.  Furthermore, there
# may be other limitations, e.g, for x86_32, OSX may limit us to far
# fewer than 8192 (got to 2558 in one test run, when egyptian_cotton
# was compiled as a standalone non-NaCl program.)
#
# The following runs the test with different numbers of threads to be
# spawned depending on the architecture.  For the Arm, it is 4095
# threads.  Note that the main thread also counts as one more thread.
#
# BUG(bsy): With the Arm architecture under qemu, spawning more than 3
# threads under will cause qemu to deadlock on exit, even though the
# test passes, so this is marked as a hardware-only test.  If we were
# to compile this using the trusted toolchain and run the resultant
# Arm binary under qemu, qemu will segfault at 375 threads.  It is
# unclear as of this writing (2010-03-11) whether this is a qemu bug
# or a limitation/bug of the codesourcery pthread library.

large_stack_args = ['-s', '0x400000', '-n', '4095', '-f' ]

#
# WARNING: Magic numbers ahead!
#
# The high_thread_count_args is used to stress test the thread library and
# the service runtime by having the test binary (egyptian_cotton.nexe)
# allocate a large number of concurrent threads.  This is done to ensure
# that there are no sudden decrease in the number of threads that can be
# created.  While limits imposed by our threading design -- 8192 for
# x86-32, and essentially unlimited for x86-64 and arm (though there's a
# statically sized table involved, so we chose to limit to 8192 as well) --
# are of our design and thus easy to determine, the underlying host
# operating system also imposes limits on the number of real underlying
# host OS threads that can be created.  Thus, the magic numbers below are
# *empirically* determined.  This is somewhat unfortunate, since it'd be
# nice to have a better understanding of the source of these limits, and
# it'd be especially nice if, for example, they could be lifted by some
# simple configuration parameter (that do not require administrative
# rights).  Nonetheless, we use the empirically determined values to detect
# changes that might further reduce the thread limit in practice.  The
# limits were obtained by simply running egyptian_cotton.nexe manually and
# seeing where it fails, and subtracting a few, so that minor,
# inconsequential and negligible changes to the runtime libraries etc won't
# require updating this test.
#

if env.Bit('build_arm'):
  # so far, only linux.
  # The arm-hw-bot was actually clocked at successfully doing 8191,
  # though it gets pretty resource-starved near the top and might
  # crash if anything else is being done on the bot, and takes a
  # really long time.  Limit to 7000 since that's more than 4096
  # but not enough to take too long.
  high_thread_count_nthreads = 7000
  platform_limits_known = True
elif env.Bit('build_mips32'):
  # Based on MIPS Malta 74Kc board.
  high_thread_count_nthreads = 3500
  platform_limits_known = True
elif env.Bit('build_x86'):
  trusted_env = env.get('TRUSTED_ENV')
  if trusted_env:
    platform_limits_known = True
    if trusted_env.Bit('linux'):
      high_thread_count_nthreads = 8180
    elif trusted_env.Bit('mac'):
      # A value of 2558 worked on OS X 10.5 (Leopard).
      # This had to be reduced to 2556 to pass on OS X 10.6 (Snow Leopard).
      # It then had to be reduced to 2044 to pass on OS X 10.7 (Lion).
      high_thread_count_nthreads = 2044
      if trusted_env.Bit('asan') and trusted_env.Bit('build_x86_32'):
        # Under ASan less address space is available.  On MacOS 10.6 on
        # x86-32 under ASan, ~1100 threads fit (there is some
        # non-determinism in the upper bound, perhaps due to ASLR).
        high_thread_count_nthreads = 1100
    elif trusted_env.Bit('windows'):
      if trusted_env.Bit('build_x86_32'):
        high_thread_count_nthreads = 700
      else:
        high_thread_count_nthreads = 900
    else:
      raise Exception('Unknown host OS: threading limit unknown')
else:
  raise Exception('Unknown target architecture -- '
                  'architecture/host OS thread limits are unknown')

if not platform_limits_known:
  Return()

# Valgrind's internal configuration variable VG_N_THREAD defaults to
# 500 (valgrind version 3.8.1), which would be insufficient to keep
# track of the high thread count used in the egyptian cotton test.
# Since this test is primarily for catching regressions where we
# reduce the maximum number of threads available to the NaCl module,
# reducing it for the valgrind tests is reasonable -- the tests aren't
# trying to expose thread race conditions or memory leaks.
# VG_N_SEGMENTS can also cause trouble, due to thread stack
# allocation.
if env.IsRunningUnderValgrind():
  high_thread_count_nthreads = min(high_thread_count_nthreads, 400)

high_thread_count_args = ['-n', str(high_thread_count_nthreads)]

if env.Bit('nacl_glibc'):
  high_thread_count_args.append('-m')
elif env.Bit('tests_use_irt'):
  # With the IRT, there is less address space available for thread stacks.
  high_thread_count_args += ['-s', str(0x2000)]

tests = [ ('egyptian_cotton_test', high_thread_count_args),
          ('thread_stack_alloc_test', large_stack_args) ]

# This test does not make much sense on Valgrind, and is too slow.
if env.IsRunningUnderValgrind():
  tests = [t for t in tests if t[0] != 'thread_stack_alloc_test']

def ThreadTestNode(name, args):
  return env.CommandSelLdrTestNacl(name + '.out',
                                   egyptian_cotton_nexe,
                                   size='large',
                                   args=args)

nodes = [ ThreadTestNode(name, args) for name, args in tests ]

for p, n in zip(tests, nodes):
  env.AddNodeToTestSuite(n, ['medium_tests', 'sel_ldr_tests'], 'run_' + p[0])
